﻿Imports Runa.UI.Controls
Imports Runa.UI.Metadata
Imports Runa.Common.Helper
Imports System.Reflection
Imports Microsoft.Xna.Framework

Namespace Global.Runa.UI.Xaml

    Public Class XamlReader

        Public Shared Function Load(ByVal xaml As String, ByVal g As Game) As UIElementBase
            Dim nodes = (From c In XElement.Parse(xaml).Elements() Select c).ToList()
            If nodes.Count <> 1 Then
                Throw New Exception("There should be one and only one root UIElement.")
            Else
                Dim rootType = nodes.First().Name
                Dim types = GetType(UIElementBase).Assembly.ExportedTypes
                For Each t In types
                    If t.Name.ToLower() = rootType.LocalName.ToLower() Then
                        Dim rootObj = Activator.CreateInstance(t, g)
                        Load(nodes.First(), rootObj)
                        Return rootObj
                    End If
                Next
                Throw New Exception("The type of the root element is undefined.")
            End If
        End Function

        Public Shared Function Load(ByVal xaml As XElement, ByVal [Me] As UIElementBase, ByVal g As Game) As Boolean
            Dim nodes = (From c In xaml.Elements() Select c).ToList()           ' Store the elements to a list.
            Dim rootNode = xaml                                                 ' The root of the xaml to be parsed.
            Dim attrs = (From a In rootNode.Attributes() Select a).ToList()     ' Get the attributes of the root node.
            Dim typ = [Me].GetType()                                            ' Get the type of the target object.

#If NETFX_CORE Then

            ' Get all properties of the target type.
            Dim targetPropertyInfos = typ.GetRuntimeProperties()

            ' Get all properties that is set to "can not be null".
            Dim nonNullFields = (From item In targetPropertyInfos
                Where item.GetCustomAttributes(GetType(XamlAwareAttribute), True).Contains(
                    Function(a As Object)
                        If TypeOf a Is XamlAwareAttribute Then
                            If CType(a, XamlAwareAttribute).CanBeNull = False Then
                                Return True
                            End If
                        End If
                        Return False
                    End Function)
                Select item).ToList()

            'Dim nonNullFields As New List(Of PropertyInfo)
            'For Each item In ps
            '    Dim [as] = item.GetCustomAttributes(GetType(XamlAwareAttribute), True)
            '    For Each a In [as]
            '        If TypeOf a Is XamlAwareAttribute Then
            '            If CType(a, XamlAwareAttribute).CanBeNull = False Then
            '                nonNullFields.Add(item)
            '            End If
            '        End If
            '    Next
            'Next

#Else

            ' Get all properties of the target type.
            Dim targetPropertyInfos = typ.GetProperties()

            ' Get all properties that is set to "can not be null".
            Dim nonNullFields = (From item In targetPropertyInfos
                Where item.GetCustomAttributes(GetType(XamlAwareAttribute), True).Contains(
                    Function(a As Object)
                        If TypeOf a Is XamlAwareAttribute Then
                            If CType(a, XamlAwareAttribute).CanBeNull = False Then
                                Return True
                            End If
                        End If
                        Return False
                    End Function)
                Select item).ToList()

            'Dim nonNullFields As New List(Of PropertyInfo)
            'For Each item In ps
            '    Dim [as] = item.GetCustomAttributes(GetType(XamlAwareAttribute), True)
            '    For Each a In [as]
            '        If TypeOf a Is XamlAwareAttribute Then
            '            If CType(a, XamlAwareAttribute).CanBeNull = False Then
            '                nonNullFields.Add(item)
            '            End If
            '        End If
            '    Next
            'Next

#End If

            ' Check if there is any properties that are tagged as "non-null" missed.
            Dim nonNullField As PropertyInfo = Nothing
            If nonNullFields.Count > 0 Then
                If nonNullFields.Contains(
                    Function(p) attrs.Contains(
                        Function(a) p.Name.ToLower() = a.Name.LocalName.ToLower()), nonNullField) = False Then
                    Throw New ArgumentException(String.Format("Must specify value for property {0}.", nonNullField.Name))
                End If
                Return False
            Else
                ' Probe the attributes of the root node.
                For Each a As XAttribute In attrs
                    For Each p As PropertyInfo In targetPropertyInfos
#If NETFX_CORE Then
                        Dim cas = p.GetCustomAttributes()
#Else
                        Dim cas = p.GetCustomAttributes(True)
#End If
                        For Each ca In cas
                            If TypeOf ca Is XamlAwareAttribute Then
                                With CType(ca, XamlAwareAttribute)
                                    If p.Name.ToLower() = a.Name.LocalName.ToLower() Or If(.ElementName, "").ToLower() = a.Name.LocalName.ToLower() Then
                                        If .IsPlainType Then
                                            Try
                                                p.SetValue([Me], a.Value)
                                            Catch ex As Exception
#If WINDOWS_PHONE Then
                                                p.SetValue([Me], Convert.ChangeType(a.Value, p.PropertyType, Nothing))
#Else
                                                p.SetValue([Me], Convert.ChangeType(a.Value, p.PropertyType))
#End If
                                            End Try
                                        Else
                                            Dim converter = CType(Activator.CreateInstance(.Initializer, g), TypeConverter)
                                            p.SetValue([Me], converter.ConvertFromString(a.Value))
                                        End If
                                    End If
                                End With
                            End If
                        Next
                    Next
                Next
                Static ctrls As List(Of Type) = _
                    (From item _
                     In GetType(UIElementBase).Assembly.ExportedTypes _
                     Where item.FullName.Contains("Runa.UI.Controls.") _
                     Select item).ToList()
                For Each n As XElement In rootNode.Elements()
                    Dim ct = ctrls.FirstOrDefault(Function(t) t.FullName.Contains(n.Name.LocalName))
                    If ct IsNot Nothing Then
                        Dim ci = CType(Activator.CreateInstance(ct, [Me]), UIElementBase)
                        Call Load(n, ci, g)
                        'If Load(n, ci, g) Then
                        '    [Me].Children.Add(ci)
                        'End If
                    Else
                        Dim currentElementType = [Me].GetType().Name
                        If n.Name.LocalName.StartsWith(currentElementType) Then
                            Dim propertyName = n.Name.LocalName.GetRight(".")
                            Dim pi = targetPropertyInfos.FirstOrDefault(Function(item) item.Name = propertyName)
                            If pi IsNot Nothing Then
                                If pi.PropertyType Is GetType(UIElementBase) Then
                                    If n.Elements().Count() = 1 Then
                                        Dim contentCtrl = n.Elements().First()
                                        ct = ctrls.FirstOrDefault(Function(t) t.FullName.Contains(contentCtrl.Name.LocalName))
                                        If ct IsNot Nothing Then
                                            Dim ci = CType(Activator.CreateInstance(ct, [Me].Game), UIElementBase)
                                            If Load(contentCtrl, ci, g) Then
                                                CType([Me], ContentControl).Content = ci
                                            End If
                                        End If
                                    End If
                                Else
                                    Try
                                        Dim propertyInstance = Activator.CreateInstance(pi.PropertyType, g)
                                        If LoadComplexProperty(n, propertyInstance, g) Then
                                            pi.SetValue([Me], propertyInstance)
                                        End If
                                    Catch ex As Exception
                                        Exit Try
                                    End Try
                                End If
                            End If
                        End If
                    End If
                Next
                [Me].UpdateLayout()
                Return True
            End If
        End Function

        Public Shared Function LoadComplexProperty(ByVal n As XElement, ByVal [Me] As Object, ByVal g As Game) As Boolean
            Dim typ = [Me].GetType()
            Dim rootNode As XElement = n
            Dim attrs = (From a In rootNode.Attributes() Select a).ToList()
#If NETFX_CORE Then
            Dim targetPropertyInfos = typ.GetRuntimeProperties()
#Else
            Dim targetPropertyInfos = typ.GetProperties()
#End If
            Dim nonNullFields = (From item In targetPropertyInfos
                Where item.GetCustomAttributes(GetType(XamlAwareAttribute), True).Contains(
                    Function(a As Object)
                        If TypeOf a Is XamlAwareAttribute Then
                            If CType(a, XamlAwareAttribute).CanBeNull = False Then
                                Return True
                            End If
                        End If
                        Return False
                    End Function)
                Select item).ToList()
            ' Check if there is any properties that are tagged as "non-null" missed.
            Dim nonNullField As PropertyInfo = Nothing
            If nonNullFields.Count > 0 Then
                If nonNullFields.Contains(
                    Function(p) attrs.Contains(
                        Function(a) p.Name.ToLower() = a.Name.LocalName.ToLower()), nonNullField) = False Then
                    Throw New ArgumentException(String.Format("Must specify value for property {0}.", nonNullField.Name))
                End If
                Return False
            Else
                ' Probe the attributes of the root node.
                For Each a As XAttribute In attrs
                    For Each p As PropertyInfo In targetPropertyInfos
#If NETFX_CORE Then
                        Dim cas = p.GetCustomAttributes()
#Else
                        Dim cas = p.GetCustomAttributes(True)
#End If
                        For Each ca In cas
                            If TypeOf ca Is XamlAwareAttribute Then
                                With CType(ca, XamlAwareAttribute)
                                    If p.Name.ToLower() = a.Name.LocalName.ToLower() Or If(.ElementName, "").ToLower() = a.Name.LocalName.ToLower() Then
                                        If .IsPlainType Then
                                            Try
                                                p.SetValue([Me], a.Value)
                                            Catch ex As Exception
#If WINDOWS_PHONE Then
                                                p.SetValue([Me], Convert.ChangeType(a.Value, p.PropertyType, Nothing))
#Else
                                                p.SetValue([Me], Convert.ChangeType(a.Value, p.PropertyType))
#End If
                                            End Try
                                        Else
                                            Dim converter = CType(Activator.CreateInstance(.Initializer, g), TypeConverter)
                                            p.SetValue([Me], converter.ConvertFromString(a.Value))
                                        End If
                                    End If
                                End With
                            End If
                        Next
                    Next
                Next
                ' Processing complex properties.
                If rootNode.Elements().Count() > 0 Then
                    For Each item In rootNode.Elements()
                        Dim currentSubProperty = item
                        If targetPropertyInfos.Contains(
                            Function(p)
                                Return p.Name.ToLower() = currentSubProperty.Name.LocalName.ToLower()
                            End Function) Then
                            Dim p = targetPropertyInfos.First(
                                Function(px)
                                    Return px.Name.ToLower() = currentSubProperty.Name.LocalName.ToLower()
                                End Function)
                            Dim cas = p.GetCustomAttributes(True)
                            Dim a = currentSubProperty.Attribute("Value")
                            For Each ca In cas
                                If TypeOf ca Is XamlAwareAttribute Then
                                    With CType(ca, XamlAwareAttribute)
                                        If .IsPlainType Then
                                            Try
                                                p.SetValue([Me], a.Value)
                                            Catch ex As Exception
#If WINDOWS_PHONE Then
                                                p.SetValue([Me], Convert.ChangeType(a.Value, p.PropertyType, Nothing))
#Else
                                                p.SetValue([Me], Convert.ChangeType(a.Value, p.PropertyType))
#End If
                                            End Try
                                        Else
                                            If .Initializer IsNot Nothing Then
                                                Dim converter = CType(Activator.CreateInstance(.Initializer, g), TypeConverter)
                                                p.SetValue([Me], converter.ConvertFromString(a.Value))
                                            Else
                                                If currentSubProperty.Elements().Count() > 0 Then
                                                    Dim propertyInstance = Activator.CreateInstance(p.PropertyType, g)
                                                    If LoadComplexProperty(currentSubProperty, propertyInstance, g) Then
                                                        p.SetValue([Me], propertyInstance)
                                                    End If
                                                End If
                                            End If
                                        End If
                                    End With
                                End If
                            Next
                        End If
                    Next
                End If
                Return True
            End If
        End Function

    End Class

End Namespace